﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using Aeon.Emulator.DebugSupport;

namespace Aeon.Emulator.Decoding
{
    /// <summary>
    /// Provides methods for decoding and emulating machine code.
    /// </summary>
    public static class InstructionSet
    {
        #region Constructors
        /// <summary>
        /// Initializes static fields on the InstructionSet class.
        /// </summary>
        static InstructionSet()
        {
            InstructionSetBuilder isb = new InstructionSetBuilder();
            isb.BuildSet();

            foreach(var info in isb.OneByteOpcodes)
                oneByteCodes[info.Opcode] = new OpcodeInfo(info);

            foreach(var info in isb.TwoByteOpcodes)
            {
                int byte1 = info.Opcode & 0xFF;
                int byte2 = (info.Opcode >> 8) & 0xFF;

                if(info.ModRmByte != ModRmInfo.OnlyRm)
                {
                    if(twoByteCodes[byte1] == null)
                        twoByteCodes[byte1] = new OpcodeInfo[256];
                    twoByteCodes[byte1][byte2] = new OpcodeInfo(info);
                }
                else
                {
                    if(twoByteRmCodes[byte1] == null)
                        twoByteRmCodes[byte1] = new OpcodeInfo[256][];
                    if(twoByteRmCodes[byte1][byte2] == null)
                        twoByteRmCodes[byte1][byte2] = new OpcodeInfo[8];
                    twoByteRmCodes[byte1][byte2][info.ExtendedRmOpcode] = new OpcodeInfo(info);
                }
            }

            foreach(var info in isb.ExtendedOpcodes)
            {
                if(rmCodes[info.Opcode] == null)
                    rmCodes[info.Opcode] = new OpcodeInfo[8];
                rmCodes[info.Opcode][info.ExtendedRmOpcode] = new OpcodeInfo(info);
            }

            allCodes = new OpcodeCollection();
        }
        #endregion

        #region Public Static Properties
        /// <summary>
        /// Gets the collection of defined opcodes.
        /// </summary>
        [CLSCompliant(false)]
        public static OpcodeCollection Opcodes
        {
            get { return allCodes; }
        }
        #endregion

        #region Public Static Methods
        /// <summary>
        /// Emulates the next instruction at CS:EIP on a virtual machine.
        /// </summary>
        /// <param name="vm">Virtual machine instance.</param>
        internal static void Emulate(VirtualMachine vm)
        {
            //var info = FindOpcode(vm.PhysicalMemory, vm.Processor);

            //var method = info.Emulators[vm.Processor.SizeModeIndex];
            //if(method != null)
            //    method(vm);
            //else
            //    throw GetPartiallyNotImplementedException(vm, info);

            var processor = vm.Processor;
            var memory = vm.PhysicalMemory;

            unsafe
            {
                uint* eip = (uint*)processor.PIP;
                uint startEIP = *eip;
                processor.StartEIP = startEIP;

                byte* ip = processor.CachedInstruction;
                memory.FetchInstruction(processor.segmentBases[(int)SegmentIndex.CS] + startEIP, ip);

                byte byte1 = ip[0];
                OpcodeInfo inst = oneByteCodes[byte1];
                if(inst != null)
                {
                    *eip = startEIP + 1;
                    processor.CachedIP = ip + 1;
                    inst.Emulators[processor.SizeModeIndex](vm);
                    return;
                }

                OpcodeInfo[] instSet = twoByteCodes[byte1];
                if(instSet != null)
                {
                    inst = instSet[ip[1]];
                    if(inst != null)
                    {
                        *eip = startEIP + 2;
                        processor.CachedIP = ip + 2;
                        inst.Emulators[processor.SizeModeIndex](vm);
                        return;
                    }
                }

                instSet = rmCodes[byte1];
                if(instSet != null)
                {
                    inst = instSet[(ip[1] & ModRmMask) >> 3];
                    if(inst == null)
                        throw GetOpcodeException(ip);

                    *eip = startEIP + 1;
                    processor.CachedIP = ip + 1;
                    inst.Emulators[processor.SizeModeIndex](vm);
                    return;
                }

                OpcodeInfo[][] instSetSet = twoByteRmCodes[byte1];
                if(instSetSet != null)
                {
                    instSet = instSetSet[ip[1]];
                    if(instSet != null)
                    {
                        inst = instSet[(ip[2] & ModRmMask) >> 3];
                        if(inst != null)
                        {
                            *eip = startEIP + 2;
                            processor.CachedIP = ip + 2;
                            inst.Emulators[processor.SizeModeIndex](vm);
                            return;
                        }
                    }
                }

                throw GetOpcodeException(ip);
            }
        }
        /// <summary>
        /// Emulates the next instruction at CS:EIP on a virtual machine.
        /// </summary>
        /// <param name="vm">Virtual machine instance.</param>
        /// <param name="log">Log to write emulated instruction information into.</param>
        internal static void Emulate(VirtualMachine vm, InstructionLog log)
        {
            var info = FindOpcode(vm.PhysicalMemory, vm.Processor);

            if(vm.Processor.CS == 0x37D7 && vm.Processor.EIP == 0x06CE)
            {
            }

            if(!info.IsPrefix)
            {
                log.SegmentOverridePrefix = vm.Processor.SegmentOverride;
                log.SizeOverridePrefix = (int)vm.Processor.SizeOverride;
                log.RepeatPrefix = vm.Processor.RepeatPrefix;
                unsafe
                {
                    log.Add(info, new IntPtr(vm.Processor.CachedIP), vm.Processor, vm.Processor.StartEIP);
                }
            }

            var method = info.Emulators[vm.Processor.SizeModeIndex];
            if(method != null)
                method(vm);
            else
                throw GetPartiallyNotImplementedException(vm, info);
        }
        /// <summary>
        /// Decodes a machine code instruction.
        /// </summary>
        /// <param name="machineCode">Machine code bytes to decode.</param>
        /// <returns>OpcodeInfo instance describing the machine code instruction if valid; null otherwise.</returns>
        internal static OpcodeInfo Decode(byte[] machineCode)
        {
            byte byte1 = machineCode[0];
            OpcodeInfo inst = oneByteCodes[byte1];
            if(inst != null)
                return inst;

            OpcodeInfo[] instSet = twoByteCodes[byte1];
            if(instSet != null)
            {
                inst = instSet[machineCode[1]];
                if(inst != null)
                    return inst;
            }

            instSet = rmCodes[byte1];
            if(instSet != null)
            {
                inst = instSet[(machineCode[1] & ModRmMask) >> 3];
                if(inst == null)
                    return null;

                return inst;
            }

            OpcodeInfo[][] instSetSet = twoByteRmCodes[byte1];
            if(instSetSet != null)
            {
                instSet = instSetSet[machineCode[1]];
                if(instSet != null)
                {
                    inst = instSet[(machineCode[2] & ModRmMask) >> 3];
                    if(inst != null)
                        return inst;
                }
            }

            return null;
        }
        #endregion

        #region Internal Native Methods
        /// <summary>
        /// Initializes arrays used for native delegate calls.
        /// </summary>
        internal static void InitializeNativeArrays()
        {
            unsafe
            {
                // Allocate first level (processor mode)
                OneBytePtrs = (IntPtr**)functionPointerAllocator.Allocate(IntPtr.Size * 4, IntPtr.Size).ToPointer();
                RmPtrs = (IntPtr***)functionPointerAllocator.Allocate(IntPtr.Size * 4, IntPtr.Size).ToPointer();
                TwoBytePtrs = (IntPtr***)functionPointerAllocator.Allocate(IntPtr.Size * 4, IntPtr.Size).ToPointer();
                TwoByteRmPtrs = (IntPtr****)functionPointerAllocator.Allocate(IntPtr.Size * 4, IntPtr.Size).ToPointer();

                for(int mode = 0; mode < 4; mode++)
                {
                    OneBytePtrs[mode] = (IntPtr*)functionPointerAllocator.Allocate(IntPtr.Size * 256, IntPtr.Size).ToPointer();
                    RmPtrs[mode] = (IntPtr**)functionPointerAllocator.Allocate(IntPtr.Size * 256, IntPtr.Size).ToPointer();
                    for(int firstByte = 0; firstByte < 256; firstByte++)
                    {
                        OneBytePtrs[mode][firstByte] = GetFunctionPointer(oneByteCodes[firstByte], mode);

                        if(rmCodes[firstByte] != null)
                        {
                            RmPtrs[mode][firstByte] = (IntPtr*)functionPointerAllocator.Allocate(IntPtr.Size * 8, IntPtr.Size).ToPointer();
                            for(int rm = 0; rm < 8; rm++)
                                RmPtrs[mode][firstByte][rm] = GetFunctionPointer(rmCodes[firstByte][rm], mode);
                        }
                    }

                    TwoBytePtrs[mode] = (IntPtr**)functionPointerAllocator.Allocate(IntPtr.Size * 256, IntPtr.Size).ToPointer();
                    for(int firstByte = 0; firstByte < 256; firstByte++)
                    {
                        if(twoByteCodes[firstByte] != null)
                        {
                            TwoBytePtrs[mode][firstByte] = (IntPtr*)functionPointerAllocator.Allocate(IntPtr.Size * 256, IntPtr.Size).ToPointer();

                            for(int secondByte = 0; secondByte < 256; secondByte++)
                                TwoBytePtrs[mode][firstByte][secondByte] = GetFunctionPointer(twoByteCodes[firstByte][secondByte], mode);
                        }
                    }

                    TwoByteRmPtrs[mode] = (IntPtr***)functionPointerAllocator.Allocate(IntPtr.Size * 256, IntPtr.Size).ToPointer();
                    for(int firstByte = 0; firstByte < 256; firstByte++)
                    {
                        if(twoByteRmCodes[firstByte] != null)
                        {
                            TwoByteRmPtrs[mode][firstByte] = (IntPtr**)functionPointerAllocator.Allocate(IntPtr.Size * 256, IntPtr.Size).ToPointer();

                            for(int secondByte = 0; secondByte < 256; secondByte++)
                            {
                                if(twoByteRmCodes[firstByte][secondByte] != null)
                                {
                                    TwoByteRmPtrs[mode][firstByte][secondByte] = (IntPtr*)functionPointerAllocator.Allocate(IntPtr.Size * 8, IntPtr.Size).ToPointer();
                                    for(int rm = 0; rm < 8; rm++)
                                        TwoByteRmPtrs[mode][firstByte][secondByte][rm] = GetFunctionPointer(twoByteRmCodes[firstByte][secondByte][rm], mode);
                                }
                            }
                        }
                    }
                }
            }
        }
        #endregion

        #region Private Static Methods
        /// <summary>
        /// Returns an exception to be thrown when an instruction is not implemented in the required mode.
        /// </summary>
        /// <param name="vm">Current VirtualMachine instance.</param>
        /// <param name="info">The partially implemented instruction.</param>
        /// <returns>Exception to be thrown.</returns>
        [MethodImpl(MethodImplOptions.NoInlining)]
        private static Exception GetPartiallyNotImplementedException(VirtualMachine vm, OpcodeInfo info)
        {
            return new NotImplementedException(string.Format("Instruction '{0}' not implemented for {1}-bit addressing, {2}-bit operand size.", info.Name, vm.Processor.AddressSize, vm.Processor.OperandSize));
        }
        /// <summary>
        /// Decodes the next instruction read by the instruction reader instance.
        /// </summary>
        /// <param name="memory">Current PhysicalMemory instance.</param>
        /// <param name="processor">Current Processor instance.</param>
        private static OpcodeInfo FindOpcode(PhysicalMemory memory, Processor processor)
        {
            unsafe
            {
                uint* eip = (uint*)processor.PIP;
                uint startEIP = *eip;
                processor.StartEIP = startEIP;

                byte* ip = processor.CachedInstruction;
                memory.FetchInstruction(processor.segmentBases[(int)SegmentIndex.CS] + startEIP, ip);

                byte byte1 = ip[0];
                OpcodeInfo inst = oneByteCodes[byte1];
                if(inst != null)
                {
                    *eip = startEIP + 1;
                    processor.CachedIP = ip + 1;
                    return inst;
                }

                OpcodeInfo[] instSet = twoByteCodes[byte1];
                if(instSet != null)
                {
                    inst = instSet[ip[1]];
                    if(inst != null)
                    {
                        *eip = startEIP + 2;
                        processor.CachedIP = ip + 2;
                        return inst;
                    }
                }

                instSet = rmCodes[byte1];
                if(instSet != null)
                {
                    inst = instSet[(ip[1] & ModRmMask) >> 3];
                    if(inst == null)
                        throw GetOpcodeException(ip);

                    *eip = startEIP + 1;
                    processor.CachedIP = ip + 1;
                    return inst;
                }

                OpcodeInfo[][] instSetSet = twoByteRmCodes[byte1];
                if(instSetSet != null)
                {
                    instSet = instSetSet[ip[1]];
                    if(instSet != null)
                    {
                        inst = instSet[(ip[2] & ModRmMask) >> 3];
                        if(inst != null)
                        {
                            *eip = startEIP + 2;
                            processor.CachedIP = ip + 2;
                            return inst;
                        }
                    }
                }

                throw GetOpcodeException(ip);
            }
        }
        /// <summary>
        /// Returns an exception to be thrown for an unhandled opcode.
        /// </summary>
        /// <param name="ip">Pointer to first byte of the opcode.</param>
        /// <returns>Throwable exception describing the error.</returns>
        private static IntPtr GetFunctionPointer(OpcodeInfo opcode, int mode)
        {
            if(opcode == null)
                return IntPtr.Zero;

            if(opcode.Emulators[mode] != null)
                return (IntPtr)typeof(Delegate).GetField("_methodPtrAux", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic).GetValue(opcode.Emulators[mode]);

            return IntPtr.Zero;
        }
        [MethodImpl(MethodImplOptions.NoInlining)]
        internal static unsafe Exception GetOpcodeException(byte* ip)
        {
            return new NotImplementedException(string.Format("Opcode {0:X2} {1:X2} ({2:X2}) not implemented.", ip[0], ip[1], ip[2]));
        }
        ///// <summary>
        ///// Copies function pointers into unsafe arrays.
        ///// </summary>
        //private static unsafe void InitializeFunctionPointers()
        //{
        //    for(int i = 0; i < 256; i++)
        //    {
        //        oneBytePtrs[i] = GetFunctionPointer(oneByteCodes[i], 0);
        //        oneBytePtrs[i + 256] = GetFunctionPointer(oneByteCodes[i], 1);
        //        oneBytePtrs[i + 256 * 2] = GetFunctionPointer(oneByteCodes[i], 2);
        //        oneBytePtrs[i + 256 * 3] = GetFunctionPointer(oneByteCodes[i], 3);

        //        if(rmCodes[i] != null)
        //        {
        //            rmPtrs[i] = (IntPtr*)Marshal.AllocCoTaskMem(IntPtr.Size * 8 * 4);

        //            for(int j = 0; j < 8; j++)
        //            {
        //                rmPtrs[i][j] = GetFunctionPointer(rmCodes[i][j], 0);
        //                rmPtrs[i][j + 8] = GetFunctionPointer(rmCodes[i][j], 1);
        //                rmPtrs[i][j + 8 * 2] = GetFunctionPointer(rmCodes[i][j], 2);
        //                rmPtrs[i][j + 8 * 3] = GetFunctionPointer(rmCodes[i][j], 3);
        //            }
        //        }
        //        else
        //            rmPtrs[i] = null;

        //        if(twoByteCodes[i] != null)
        //        {
        //            twoBytePtrs[i] = (IntPtr*)Marshal.AllocCoTaskMem(IntPtr.Size * 256 * 4);

        //            for(int j = 0; j < 256; j++)
        //            {
        //                twoBytePtrs[i][j] = GetFunctionPointer(twoByteCodes[i][j], 0);
        //                twoBytePtrs[i][j + 256] = GetFunctionPointer(twoByteCodes[i][j], 1);
        //                twoBytePtrs[i][j + 256 * 2] = GetFunctionPointer(twoByteCodes[i][j], 2);
        //                twoBytePtrs[i][j + 256 * 3] = GetFunctionPointer(twoByteCodes[i][j], 3);
        //            }
        //        }
        //        else
        //            twoBytePtrs[i] = null;

        //        if(twoByteRmCodes[i] != null)
        //        {
        //            twoByteRmPtrs[i] = (IntPtr**)Marshal.AllocCoTaskMem(IntPtr.Size * 256);

        //            for(int j = 0; j < 256; j++)
        //            {
        //                if(twoByteRmCodes[i][j] != null)
        //                {
        //                    twoByteRmPtrs[i][j] = (IntPtr*)Marshal.AllocCoTaskMem(IntPtr.Size * 8 * 4).ToPointer();

        //                    for(int k = 0; k < 8; k++)
        //                    {
        //                        twoByteRmPtrs[i][j][k] = GetFunctionPointer(twoByteRmCodes[i][j][k], 0);
        //                        twoByteRmPtrs[i][j][k + 8] = GetFunctionPointer(twoByteRmCodes[i][j][k], 1);
        //                        twoByteRmPtrs[i][j][k + 8 * 2] = GetFunctionPointer(twoByteRmCodes[i][j][k], 2);
        //                        twoByteRmPtrs[i][j][k + 8 * 3] = GetFunctionPointer(twoByteRmCodes[i][j][k], 3);
        //                    }
        //                }
        //                else
        //                    twoByteRmPtrs[i][j] = null;
        //            }
        //        }
        //        else
        //            twoByteRmPtrs[i] = null;
        //    }
        //}
        ///// <summary>
        ///// Returns a function pointer to the emulate method if defined.
        ///// </summary>
        ///// <param name="info">Opcode to get function pointer for.</param>
        ///// <param name="index">Opcode mode index.</param>
        ///// <returns>Function pointer to the emulate method if defined; otherwise null.</returns>
        //private static IntPtr GetFunctionPointer(OpcodeInfo info, int index)
        //{
        //    if(info == null)
        //        return IntPtr.Zero;

        //    if(info.Emulators[index] == null)
        //        return IntPtr.Zero;

        //    var ptrField = typeof(Delegate).GetField("_methodPtrAux", BindingFlags.NonPublic | BindingFlags.Instance);
        //    var ptr = (IntPtr)ptrField.GetValue(info.Emulators[index]);
        //    return ptr;
        //}
        #endregion

        #region Private Static Fields
        private static readonly OpcodeInfo[] oneByteCodes = new OpcodeInfo[256];
        private static readonly OpcodeInfo[][] rmCodes = new OpcodeInfo[256][];
        private static readonly OpcodeInfo[][] twoByteCodes = new OpcodeInfo[256][];
        private static readonly OpcodeInfo[][][] twoByteRmCodes = new OpcodeInfo[256][][];
        private static readonly OpcodeCollection allCodes;

        private static readonly NativeHeap functionPointerAllocator = new NativeHeap(122880);
        internal unsafe static IntPtr** OneBytePtrs;
        internal unsafe static IntPtr*** RmPtrs;
        internal unsafe static IntPtr*** TwoBytePtrs;
        internal unsafe static IntPtr**** TwoByteRmPtrs;
        #endregion

        #region Private Constants
        private const int ModRmMask = 0x38;
        #endregion

        /// <summary>
        /// Contains all defined instruction opcodes.
        /// </summary>
        [CLSCompliant(false)]
        public sealed class OpcodeCollection : IEnumerable<OpcodeInfo>
        {
            #region Private Fields
            private int count;
            private readonly IEnumerable<OpcodeInfo> allCodes;
            #endregion

            #region Constructors
            /// <summary>
            /// Initializes a new instance of the OpcodeCollection class.
            /// </summary>
            internal OpcodeCollection()
            {
                var codes = oneByteCodes.Where(IsNotNull);
                codes = Append(codes, Expand(twoByteCodes.Where(IsNotNull)).Where(IsNotNull));
                codes = Append(codes, Expand(rmCodes.Where(IsNotNull)).Where(IsNotNull));
                codes = Append(codes, Expand(Expand(twoByteRmCodes.Where(IsNotNull)).Where(IsNotNull)).Where(IsNotNull));

                this.allCodes = codes;
            }
            #endregion

            #region Public Indexers
            /// <summary>
            /// Gets information about an opcode.
            /// </summary>
            /// <param name="opcode">Opcode to retrieve.</param>
            /// <returns>Information about the supplied opcode if defined; otherwise null.</returns>
            public OpcodeInfo this[int opcode]
            {
                get
                {
                    return InstructionSet.Decode(BitConverter.GetBytes(opcode));
                }
            }
            #endregion

            #region Public Properties
            /// <summary>
            /// Gets the number of opcodes defined.
            /// </summary>
            public int Count
            {
                get
                {
                    if(this.count == 0)
                        this.count = this.Count();
                    return this.count;
                }
            }
            #endregion

            #region Public Methods
            /// <summary>
            /// Returns an enumerator for all defined opcodes.
            /// </summary>
            /// <returns>Enumerator for all defined opcodes.</returns>
            public IEnumerator<OpcodeInfo> GetEnumerator()
            {
                return this.allCodes.GetEnumerator();
            }
            /// <summary>
            /// Returns an enumerator for all defined opcodes.
            /// </summary>
            /// <returns>Enumerator for all defined opcodes.</returns>
            System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
            {
                return GetEnumerator();
            }
            #endregion

            #region Private Static Methods
            /// <summary>
            /// Returns a value indicating whether an object is not a null reference.
            /// </summary>
            /// <param name="obj">Reference to test for null.</param>
            /// <returns>True if object reference is not null; otherwise false.</returns>
            private static bool IsNotNull(object obj)
            {
                return obj != null;
            }
            /// <summary>
            /// Appends two collections together.
            /// </summary>
            /// <typeparam name="T">Type of item in collections.</typeparam>
            /// <param name="source1">First collection.</param>
            /// <param name="source2">Second collection.</param>
            /// <returns>Contents of both collections.</returns>
            private static IEnumerable<T> Append<T>(IEnumerable<T> source1, IEnumerable<T> source2)
            {
                foreach(var item in source1)
                    yield return item;

                foreach(var item in source2)
                    yield return item;
            }
            /// <summary>
            /// Expands a collection of collections.
            /// </summary>
            /// <typeparam name="T">Type of item in collections.</typeparam>
            /// <param name="source">Collection of collections to expand.</param>
            /// <returns>Contents of all contained collections.</returns>
            private static IEnumerable<T> Expand<T>(IEnumerable<IEnumerable<T>> source)
            {
                foreach(var items in source)
                {
                    foreach(var item in items)
                        yield return item;
                }
            }
            #endregion
        }
    }
}
